﻿using System;
using System.Collections;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using System.Reflection;

namespace Usgen
{
    class Program
    {

        static Program()
        {
            IsDomain = CheckDomain();

            if (IsDomain)
            {
                try
                {
                    m_CNDomain = FormatDomainName();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("An error has occurred: {0}", ex.Message);
                }
            }
        }

        private static string FormatDomainName()
        {
            string name = Domain.GetCurrentDomain().Name.Split('.').Aggregate(string.Empty, (Current, s) => String.Concat(Current, String.Format("DC={0},", s)));
            name = name.Remove(name.Length - 1);
            return name;
        }


        private static bool CheckDomain()
        {
            try
            {
                Domain d = Domain.GetComputerDomain();
                return true;
            }
            catch (ActiveDirectoryObjectNotFoundException)
            {
                return false;
            }                
        }

        private static bool IsDomain
        {
            get;set;
        }

        static void Main(string[] args)
        {
            if (args.Length < 2 || args.Length > 5)
            {
                if (args.Length == 1)
                {
                    switch (args[0])
                    {
                        case "-h":
                        case "--h":
                        case "/?":

                        Usage();
                        return;
                    }
                }

                Usage();
                return;
            }

            if (!IsDomain)
            {
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.WriteLine("WARNING! This computer is not domain member");
                Console.ResetColor();
                return;
            }

            try
            {
                // detect action type
                var action = GetActionType(args[0]);

                // detect object type
                var mETargetObjectType = GetObjectType(args[1]);
                
                var LastParams = new string[args.Length - 2];
                Array.Copy(args, 2, LastParams, 0, LastParams.Length);

                // Operation mode
                switch (action)
                {
                    case EAction.Add:
                        AddObjects(mETargetObjectType, LastParams);
                        break;

                    case EAction.Delete:
                        RemoveObjects(mETargetObjectType, LastParams);
                        break;

                    default:
                        throw new Exception("Invalid action");
                }
            }

            catch(InvalidArgumentException e)
            {
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.WriteLine(e.Message);
                Console.ResetColor();
                //Usage();
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.WriteLine("An error has occurred: '{0}'", ex.Message);
                Console.ResetColor();
            }
        }

        private static EAction GetActionType(string s)
        {
            EAction res;
            switch (s)
            {
                case "-a": res = EAction.Add; break;
                case "-r": res = EAction.Delete; break;
                default:
                    throw new InvalidArgumentException(string.Format("Invalid action argument '{0}'", s));
            }

            return res;
        }

        private static ETargetObjectType GetObjectType(string s)
        {
            ETargetObjectType res;
            switch (s)
            {
                case "-u":
                    res = ETargetObjectType.Users;
                    break;

                case "-w": res = ETargetObjectType.Workstations;
                    break;

                default:
                    throw new InvalidArgumentException(string.Format("Invalid object argument '{0}'", s));
            }
            return res;
        }


        static void AddObjects(ETargetObjectType enMode, string[] args)
        {

            string MaskName = String.Concat("Test", (enMode == ETargetObjectType.Users) ? "User" : "Computer");

            string OU = string.Empty;
            string objectname = (enMode == ETargetObjectType.Users) ? "users" : "workstations";

            uint Count = 50;

            switch (args.Length)
            {
                case 0:
                    //MaskName = (enMode == ETargetObjectType.Users) ? MaskName + "Users" : MaskName + "Workstation";
                    throw new InvalidArgumentException("Invalid argument 'Count'");
                case 1: // Count
                    Count = ParseCount(args[0]);
                    break;
                case 2: // Mask
                    Count = ParseCount(args[0]);
                    MaskName = args[1];                    
                    break;
                case 3: // Mask, Count, OU
                    Count = ParseCount(args[0]);
                    MaskName = args[1];                    
                    OU = args[2];
                    break;
                default:
                    Usage();
                    return;
            }

            Console.WriteLine("We are going to register {0} {1} with name template like '{2}', OU:{3}", 
                              Count,
                              objectname, 
                              MaskName, 
                              (string.IsNullOrEmpty(OU) ? "Not specified" : OU));

            uint SucceededCount = 0;

            DirectoryEntry parent;
            Hashtable childsHash = null;

            try
            {
                parent = ADEntry(OU, enMode);
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Error has occurred during initialize DirectoryEntry. Details: '{0}'", ex.Message));
            }

            for (int i = 1; i <= Count; i++)
            {
                string name = String.Concat(MaskName, (i).ToString());

                try
                {
                    RegisterObjectAD(name, enMode, parent);
                    SucceededCount++;
                }
                catch (Exception ex)
                {
                    throw new Exception(String.Format("Adding object with the name '{0}' failed with exception '{1}'", name, ex.Message));
                }
                finally
                {
                    if (i % 100 == 0 && i > 0)
                    {
                        Console.WriteLine("\tProcessed {0} {1}..", i, objectname);
                    }
                }
            }

            Console.ForegroundColor = ConsoleColor.DarkGreen;
            Console.WriteLine("There are {0} {1} registered", SucceededCount, (enMode == ETargetObjectType.Users) ? "users" : "workstations");
            Console.ResetColor();
        }

        private static uint ParseCount(string s)
        {
            uint Count;

            if (!uint.TryParse(s, out Count))
            {
                throw new InvalidArgumentException("Invalid count argument: " + s);
            }

            return Count;
        }


        static void RemoveObjects(ETargetObjectType enMode, string[] args)
        {
            string MaskName = string.Empty;

            String OU = string.Empty;

            switch (args.Length)
            {
                case 0:
                    throw new InvalidArgumentException("Invalid argument. Use mask");

                case 1:
                    MaskName = args[0];
                    UnRegisterObjectAD(MaskName, enMode, OU);
                    break;

                case 2:
                    MaskName = args[0];
                    OU = args[1];
                    UnRegisterObjectAD(MaskName, enMode, OU);
                    break;

                default:
                    Usage();
                    return;
            }
        }

        private static readonly string m_CNDomain;

        private static string CNDomain
        {
            get
            {
                return m_CNDomain;
            }
        }

        private static DirectoryEntry ADEntry(string OU, ETargetObjectType enMode)
        {
            string rec = string.Empty;

            return new DirectoryEntry(String.IsNullOrEmpty(OU)
                      ? string.Format("LDAP://CN={0},{1}", (enMode == ETargetObjectType.Users) ? "Users" : "Computers", CNDomain.ToLower())
                      : string.Format("LDAP://OU={0},{1}", OU, CNDomain.ToLower()));

        }

        private static void RegisterObjectAD(string cnName, ETargetObjectType enMode, DirectoryEntry parent)
        {
            if (String.IsNullOrEmpty(cnName))
                throw new Exception("Invalid object name");

            //if (parent.Children.Cast<DirectoryEntry>().Any(entry => entry.Name.ToUpper().Trim().Contains(cnName.ToUpper().Trim())))
            //{
            //    throw new Exception(string.Format("AD object with the name '{0}' already exist", cnName));
            //}

            DirectoryEntry comp = parent.Children.Add(String.Concat("CN=", cnName), (enMode == ETargetObjectType.Users) ? "user" : "computer");

            using (comp)
            {

                if (enMode == ETargetObjectType.Users)
                {
                    comp.Properties["userPrincipalName"].Value = cnName;
                    comp.Properties["samAccountName"].Value = cnName; 
                    

                    comp.Properties["displayname"].Value = cnName;
                    comp.Properties["name"].Value = cnName;
                    comp.Properties["mail"].Value = string.Concat(cnName, string.Concat("@", Domain.GetCurrentDomain().Name));
                }
                else
                {

                    // Enable station account
                    comp.Properties["userAccountControl"].Value = 4128;  
                }

                comp.CommitChanges();

                if (enMode == ETargetObjectType.Users)
                {

                    comp.Invoke("SetPassword", new object[] { String.Empty });
                    int val = (int)comp.Properties["userAccountControl"].Value;
                    comp.Properties["userAccountControl"].Value = val & ~0x0002; ;

                    comp.CommitChanges();
                }
            }
        }

        private static void UnRegisterObjectAD(string CNName, ETargetObjectType enMode, String OU)
        {
            if (String.IsNullOrEmpty(CNName))
            {
                throw new Exception("Invalid object name");
            }

            DirectoryEntry parent = ADEntry(OU, enMode);

            uint SucceededCount = 0;

            try
            {
                int i = 0;
                foreach (DirectoryEntry entry in parent.Children.Cast<DirectoryEntry>().Where(entry => entry.Name.ToUpper().Trim().Contains(CNName.ToUpper().Trim())))
                {
                    parent.Children.Remove(entry);
                    SucceededCount++;
                    i++;
                    if (i % 100 == 0 && i > 0) Console.WriteLine("\tProcessed {0} objects..", i);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Unregistering AD object with '{0}' failed with exception. Description: {1}", CNName, ex.Message));
            }
            finally
            {
                Console.WriteLine("There are {0} AD objects unregistered", SucceededCount);
            }
        }

        static void Usage()
        {
            string exename = Assembly.GetExecutingAssembly().GetName().Name + ".exe";

            Console.ForegroundColor = ConsoleColor.DarkMagenta;
            Console.WriteLine("This program can add and remove users and workstations in the current domain.");
            Console.ResetColor();

            Console.WriteLine("Add objects: {0} -a -u|-w <count> [<name>] [<OU>]", exename);
            Console.WriteLine("Remove objects: {0} -r -u|-w <name> [<OU>]", exename);

            Console.WriteLine("\tArguments:");
            Console.WriteLine("\t-a\tAdd objects");
            Console.WriteLine("\t-r\tRemove objects");
            Console.WriteLine("\t-u\tUsers");
            Console.WriteLine("\t-w\tWorkstations");
            Console.WriteLine("\t<name>\tObject name template");
            Console.WriteLine("\t<count>\tObjects count");
            Console.WriteLine("\tOU\tOrganization unit name");

            Console.WriteLine("Examples:\n");
            Console.WriteLine("Add 50 users with default names like \"TestUserX\" run: {0} -a -u 50", exename);
            Console.WriteLine("Add 15 workstations with prefix name 'TestComp': {0} -a -w 15 \"TestComp\"", exename);            
            Console.WriteLine("Add 100 workstations with name like 'TestStation' in OU \"HR\": {0} -a -w 100 \"TestStation\" \"HR\"", exename);
            Console.WriteLine("Remove all workstations with the name like 'TestSt': {0} -r -w \"TestSt\"", exename);
            Console.WriteLine("Remove all workstations with the name like 'TestSt' from OU \"HR\": {0} -r -w \"TestSt\" \"HR\"", exename);
        }
    }
}
